;; -*- lexical-binding: t -*-
;; term
(leaf vterm
  :ensure t
  :after (vterm-toggle)
  :setq ((vterm-buffer-name-string . "#vterm %s"))
  :bind ((vterm-mode-map
          ("<escape>" . #'god-mode-all)
          ("C-g" . #'vterm-send-escape))
         (vterm-copy-mode-map
          ("<escape>" . #'god-mode-all)))
  :preface
  (defun find-file-goto-line (path line)
    (find-file path)
    (forward-line (- (string-to-number line) 1)))
  (defun vterm-tramp (path)
    (interactive "sPath: ")
    (let ((default-directory path))
      (vterm)))
  :init
  (advice-add
   #'vterm--redraw :around
   (lambda (fun &rest args) (let ((cursor-type cursor-type)) (apply fun args))))
  :config
  (defface nerd-icons-font
    '((t :family "Hack Nerd Font"))
    "Hack Nerd"
    :group 'basic-faces)
  (add-hook
   'vterm-mode-hook
   (lambda ()
     (set (make-local-variable 'buffer-face-mode-face) 'nerd-icons-font)
     (buffer-face-mode t)))
  (set-face-foreground 'vterm-color-bright-black "#5B6268")
  (set-face-background 'vterm-color-bright-black "#5B6268")
  (set-face-foreground 'vterm-color-black "#5B6268")
  (set-face-background 'vterm-color-black "#5B6268")
  (add-to-list 'vterm-eval-cmds '("magit-status" magit-status))
  (add-to-list 'vterm-eval-cmds '("find-file-goto-line" find-file-goto-line))
  (add-to-list 'vterm-eval-cmds '("vterm-tramp" vterm-tramp)))

;; 弹出 term
(leaf vterm-toggle
  :ensure t
  :bind (("s-t" . vterm-toggle)
         ("H-t" . vterm-toggle)
         ("C-s-v" . vterm-toggle)))

(leaf vterm-edit-command
  :require t
  :after vterm)

(leaf editorconfig
  :ensure t
  :setq (editorconfig-trim-whitespaces-mode . 'ws-butler-mode)
  :config
  (editorconfig-mode 1))

;; LSP
;; TODO dap-mode
(leaf lsp-mode
  :ensure t
  :preface
  ;; https://github.com/blahgeek/emacs-lsp-booster
  (defun lsp-booster--advice-json-parse (old-fn &rest args)
    "Try to parse bytecode instead of json."
    (or
     (when (equal (following-char) ?#)
       (let ((bytecode (read (current-buffer))))
         (when (byte-code-function-p bytecode)
           (funcall bytecode))))
     (apply old-fn args)))
  (advice-add (if (progn (require 'json)
                         (fboundp 'json-parse-buffer))
                  'json-parse-buffer
                'json-read)
              :around
              #'lsp-booster--advice-json-parse)
  (defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
    "Prepend emacs-lsp-booster command to lsp CMD."
    (let ((orig-result (funcall old-fn cmd test?)))
      (if (and (not test?)                             ;; for check lsp-server-present?
               (not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper
               lsp-use-plists
               (not (functionp 'json-rpc-connection))  ;; native json-rpc
               (executable-find "emacs-lsp-booster"))
          (progn
            (message "Using emacs-lsp-booster for %s!" orig-result)
            (cons "emacs-lsp-booster" orig-result))
        orig-result)))
  (advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command)
  ;; elixir
  (defun open-content-in-org-mode (content)
    (let ((lsp-help-buf-name "*lsp-elixir-macro-expand*"))
      (with-current-buffer (get-buffer-create lsp-help-buf-name)
        (with-help-window lsp-help-buf-name
          (insert content)
          (org-mode)
          (use-local-map (copy-keymap org-mode-map))
          (local-set-key "q" 'quit-window)))))
  (defun elixir-org-mode-code-block (header code)
    (concat "* " header "\n"
            "#+begin_src elixir\n"
            code
            "#+end_src\n"))
  (defun lsp-elixir-expand-macro (start end)
    (interactive "r")
    (let* ((uri (lsp--buffer-uri))
           (text (buffer-substring start end))
           (line (- (line-number-at-pos) 1)) ;; elixir-ls expects line numbers to start at 0
           (result (lsp-send-execute-command "expandMacro:emacs" (vector uri text line)))
           (expand (plist-get result :expand))
           (expand-all (plist-get result :expandAll))
           (expand-once (plist-get result :expandOnce))
           (expand-partial (plist-get result :expandPartial)))
      (open-content-in-org-mode
       (concat (elixir-org-mode-code-block "Expand" expand) "\n"
               (elixir-org-mode-code-block "Expand Once" expand-once) "\n"
               (elixir-org-mode-code-block "Expand Partial" expand-partial) "\n"
               (elixir-org-mode-code-block "Expand All" expand-all)))))
  (defun my--lsp-imenu-create-categorized-index (symbols)
    (let ((syms (lsp--imenu-create-categorized-index-1 symbols)))
      (dolist (sym syms)
        (let* ((kind (car sym))
               (items (cadr sym))
               (kind-name (lsp--imenu-kind->name kind)))
          (if (eq 12 kind)
              (setcdr sym (list
                           (cons
                            (car items)
                            (-group-by (lambda (fun)
                                         (if (string-prefix-p "defp" (car fun))
                                             "Private"
                                           "Public"))
                                       (cdr items))))))
          (setcar sym kind-name)))
      syms))
  ;; go format
  (defun lsp-golang-save-hook nil
    (add-hook 'before-save-hook #'lsp-format-buffer t t)
    (add-hook 'before-save-hook #'lsp-organize-imports t t))
  ;; doc
  (defun lsp--describe-thing-at-point! ()
    (interactive)
    (lsp-describe-thing-at-point)
    (with-current-buffer (get-buffer-create "*lsp-help*")
      (lsp-ui-doc-frame-mode)))
  :bind ((lsp-mode-map
          ("C-h d" . lsp--describe-thing-at-point!)))
  :commands (lsp lsp-org)
  :hook ((go-mode-hook . lsp-golang-save-hook)
         ((elixir-mode-hook
           elixir-ts-mode-hook
           heex-ts-mode-hook
           js-mode-hook
           css-mode-hook
           scss-mode-hook
           go-mode-hook
           erlang-mode-hook
           sql-mode-hook
           enh-ruby-mode-hook
           typescript-mode-hook
           typescript-ts-mode-hook
           tsx-ts-mode-hook
           typescript-tsx-ts-mode-hook) . lsp))
  :setq-default ((lsp-prefer-flymake)
                 (lsp-eldoc-render-all)
                 (lsp-tcp-connection-timeout . 60)
                 (lsp-semantic-tokens-enable . t))
  :setq `((lsp-session-file . ,(cache-path ".lsp-session-v1"))
          (lsp-headerline-arrow . ,(propertize "❯" 'face '((t :inherit font-lock-doc-face))))
          (lsp-headerline-breadcrumb-enable . nil)
          (lsp-signature-auto-activate . '(:on-server-request))
          (lsp-imenu-index-function . #'my--lsp-imenu-create-categorized-index)
          ;; (lsp-enable-snippet . nil)
          ;; (lsp-enable-file-watchers . nil)
          ;; (lsp-file-watch-threshold . 2000)
          )
  :config
  ;; typescript
  (add-to-list 'lsp--formatting-indent-alist '(tsx-ts-mode . typescript-indent-level))
  (add-to-list 'lsp--formatting-indent-alist '(typescript-tsx-ts-mode . typescript-indent-level))
  ;; ingnore files
  (add-to-list 'lsp-file-watch-ignored "[/\\\\]_build$")
  (add-to-list 'lsp-file-watch-ignored "[/\\\\]\\.elixir_ls$")
  (add-to-list 'lsp-file-watch-ignored "[/\\\\]\\.lexical$")
  (add-to-list 'lsp-file-watch-ignored "[/\\\\]deps")
  (add-to-list 'lsp-file-watch-ignored "[/\\\\]\\.cache$")
  (add-to-list 'lsp-file-watch-ignored "[/\\\\]\\.asdf/installs/elixir"))

(leaf lsp-ui
  :ensure t
  :preface
  (defun lsp-ui-toggle-doc ()
    (interactive)
    (if lsp-ui-doc-mode
        (progn
          (lsp-ui-doc-mode -1)
          (lsp-ui-doc-hide))
      (lsp-ui-doc-mode 1)))
  :commands lsp-ui-mode
  :hook ((lsp-ui-mode-hook . lsp-ui-toggle-doc))
  :bind ((lsp-ui-doc-frame-mode-map
          ("q" . winner-undo)))
  :setq
  (lsp-ui-sideline-show-code-actions . t))

(leaf lsp-treemacs
  :ensure t
  :after (lsp-mode)
  :commands lsp-treemacs-errors-list
  :init
  (lsp-treemacs-sync-mode 1))

(leaf lsp-ivy
  :ensure t
  :after (lsp-mode)
  :commands lsp-ivy-workspace-symbol)

;; Treesit (tree sitter)
(leaf treesit
  :init
  (setq treesit-language-source-alist '()))

;; company
(leaf company
  :ensure t
  :preface
  ;; set yasnippet's priority, expand yasnippet before company
  ;; https://emacs-china.org/t/company/10487/5
  (defun company//complete-common ()
    (interactive)
    (unless (ignore-errors (yas-expand))
      (company-complete-common)))
  :bind ((company-active-map
          ([tab] . company//complete-common)))
  :setq ((company-idle-delay . 0.2)
         (company-minimum-prefix-length . 1)
         (company-show-numbers))
  :init
  (global-company-mode))

(leaf company-box
  :ensure t
  :require t
  :when window-system
  :hook company-mode-hook
  :preface
  (defun +company-box-icons--elisp-fn (candidate)
    (when (derived-mode-p 'emacs-lisp-mode)
      (let ((sym (intern candidate)))
        (cond ((fboundp sym)  'ElispFunction)
              ((boundp sym)   'ElispVariable)
              ((featurep sym) 'ElispFeature)
              ((facep sym)    'ElispFace)))))
  (defun replace-end-image-space-with-X (string)
    "Replace the last space in STRING with an X if it has text properties."
    (if-let* ((end-pos (- (length string) 1))
              (blank? (string= (substring string end-pos) " "))
              (text-properties (text-properties-at end-pos string)))
        ;; replace the last space with an X inplace
        (store-substring string end-pos "X")
      string))
  (defun company-box--make-candidate! (candidate)
    (let* ((annotation (-some->> (company-call-backend 'annotation candidate)
                         (replace-end-image-space-with-X) ; added this line
                         (replace-regexp-in-string "[ \t\n\r]+" " ")
                         (string-trim)))
           (len-candidate (string-width candidate))
           (len-annotation (if annotation ; use string-pixel-width instead of string-width
                               (/ (string-pixel-width annotation)
                                  (frame-char-width))
                             0))
           (len-total (+ len-candidate len-annotation))
           (backend (company-box--backend candidate)))
      (when (> len-total company-box--max)
        (setq company-box--max len-total))
      (list candidate
            annotation
            len-candidate
            len-annotation
            backend)))
  (advice-add
   #'company-box--make-candidate :override
   #'company-box--make-candidate!)
  :config
  (setq company-tooltip-align-annotations t
        company-box-show-single-candidate t
        company-box-backends-colors nil
        company-box-icons-alist 'company-box-icons-nerd-icons
        company-box-icons-functions
        (cons #'+company-box-icons--elisp-fn
              (delq 'company-box-icons--elisp
                    company-box-icons-functions))
        company-box-icons-nerd-icons
        `((Unknown       . ,(nerd-icons-codicon "nf-cod-ellipsis"))
          (Text          . ,(nerd-icons-codicon "nf-cod-symbol_string"))
          (Method        . ,(nerd-icons-codicon "nf-cod-symbol_method"      :face 'nerd-icons-purple))
          (Function      . ,(nerd-icons-codicon "nf-cod-symbol_method"      :face 'nerd-icons-purple))
          (Constructor   . ,(nerd-icons-codicon "nf-cod-symbol_method"      :face 'nerd-icons-lpurple))
          (Field         . ,(nerd-icons-codicon "nf-cod-symbol_field"       :face 'nerd-icons-lblue))
          (Variable      . ,(nerd-icons-codicon "nf-cod-symbol_variable"    :face 'nerd-icons-lblue))
          (Class         . ,(nerd-icons-codicon "nf-cod-symbol_class"       :face 'nerd-icons-orange))
          (Interface     . ,(nerd-icons-codicon "nf-cod-symbol_interface"   :face 'nerd-icons-lblue))
          (Module        . ,(nerd-icons-codicon "nf-cod-symbol_namespace"   :face 'nerd-icons-lblue))
          (Property      . ,(nerd-icons-codicon "nf-cod-symbol_property"))
          (Unit          . ,(nerd-icons-codicon "nf-cod-symbol_key"))
          (Value         . ,(nerd-icons-codicon "nf-cod-symbol_numeric"     :face 'nerd-icons-lblue))
          (Enum          . ,(nerd-icons-codicon "nf-cod-symbol_enum"        :face 'nerd-icons-orange))
          (Keyword       . ,(nerd-icons-codicon "nf-cod-symbol_keyword"))
          (Snippet       . ,(nerd-icons-codicon "nf-cod-symbol_snippet"))
          (Color         . ,(nerd-icons-codicon "nf-cod-symbol_color"))
          (File          . ,(nerd-icons-codicon "nf-cod-symbol_file"))
          (Reference     . ,(nerd-icons-codicon "nf-cod-symbol_misc"))
          (Folder        . ,(nerd-icons-codicon "nf-cod-folder"))
          (EnumMember    . ,(nerd-icons-codicon "nf-cod-symbol_enum_member" :face 'nerd-icons-lblue))
          (Constant      . ,(nerd-icons-codicon "nf-cod-symbol_constant"))
          (Struct        . ,(nerd-icons-codicon "nf-cod-symbol_structure"   :face 'nerd-icons-orange))
          (Event         . ,(nerd-icons-codicon "nf-cod-symbol_event"       :face 'nerd-icons-orange))
          (Operator      . ,(nerd-icons-codicon "nf-cod-symbol_operator"))
          (TypeParameter . ,(nerd-icons-codicon "nf-cod-symbol_class"))
          (Template      . ,(nerd-icons-codicon "nf-cod-symbol_snippet"))
          (ElispFunction . ,(nerd-icons-codicon "nf-cod-symbol_method"      :face 'nerd-icons-purple))
          (ElispVariable . ,(nerd-icons-codicon "nf-cod-symbol_variable"    :face 'nerd-icons-lblue))
          (ElispFeature  . ,(nerd-icons-codicon "nf-cod-star_full"          :face 'nerd-icons-orange))
          (ElispFace     . ,(nerd-icons-codicon "nf-cod-symbol_color"       :face 'nerd-icons-pink))))
  (delq 'company-echo-metadata-frontend company-frontends))

(leaf company-shell
  :ensure t
  :setq (company-shell-modes . '(sh-mode shell-mode eshell-mode vterm-mode))
  :preface
  (defun company-vterm-callback (str)
    (if (eq major-mode 'vterm-mode)
        (vterm-send-string (substring str (length vterm-company-prefix)) t))
    (setq-local vterm-company-prefix nil))
  (defun company-shell! (command &optional arg &rest ignored)
    "Company mode backend for binaries found on the $PATH."
    (interactive (list 'interactive))
    (cl-case command
      (interactive (company-begin-backend 'company-shell!))
      (prefix      (company-shell--prefix company-shell-modes))
      (sorted      t)
      (duplicates  nil)
      (ignore-case nil)
      (no-cache    nil)
      (annotation  (get-text-property 0 'origin arg))
      (doc-buffer  (company-shell--doc-buffer arg))
      (meta        (company-shell--meta-string arg))
      (candidates  (progn
                     (setq-local vterm-company-prefix arg)
                     (cl-remove-if-not
                      (lambda (candidate) (string-prefix-p arg candidate))
                      (company-shell--fetch-candidates))))
      (post-completion (company-vterm-callback arg))))
  (advice-add 'company-shell :override #'company-shell!)
  (defun company-shell-env! (command &optional arg &rest ignored)
    "Company backend for environment variables."
    (interactive (list 'interactive))
    (cl-case command
      (interactive (company-begin-backend 'company-shell-env!))
      (prefix      (company-shell--prefix company-shell-modes))
      (sorted      t)
      (duplicates  nil)
      (ignore-case nil)
      (no-cache    nil)
      (annotation  "Environment Variable")
      (doc-buffer  nil)
      (meta        nil)
      (candidates  (progn
                     (setq-local vterm-company-prefix arg)
                     (cl-remove-if-not
                      (lambda (candidate) (string-prefix-p arg candidate))
                      (company-shell--fetch-env-candidates))))))
  (advice-add 'company-shell-env :override #'company-shell-env!)
  (defun company--should-complete! ()
    (and (eq company-idle-delay 'now)
         (or (eq major-mode 'vterm-mode)
             (not (or buffer-read-only
                      overriding-local-map)))
         ;; Check if in the middle of entering a key combination.
         (or (equal (this-command-keys-vector) [])
             (not (keymapp (key-binding (this-command-keys-vector)))))
         (not (and transient-mark-mode mark-active))))
  (advice-add 'company--should-complete :override #'company--should-complete!)
  (defun company-shell--doc-buffer-man! (arg)
    "Create a company doc buffer for ARG."
    (let* ((man-text (shell-command-to-string (format "man %s" arg)))
           (buf-text (if (or (null man-text)
                             (string= man-text "")
                             (string-prefix-p "No manual entry" man-text))
                         (company-shell--help-page arg)
                       man-text))
           (doc-buf (company-doc-buffer buf-text)))
      (when company-shell-clean-manpage
        (with-current-buffer doc-buf (Man-cleanup-manpage)))
      doc-buf))
  (defun company-shell--doc-buffer! (arg)
    (let ((file-path (tldr-get-file-path-from-command-name arg)))
      (if file-path
          (company-doc-buffer (tldr-render-markdown arg))
        (company-shell--doc-buffer-man! arg))))
  (advice-add 'company-shell--doc-buffer :override #'company-shell--doc-buffer!)
  :config
  (add-to-list 'company-backends '(company-shell company-shell-env)))

(leaf tldr
  :ensure t
  :require t
  :setq `(tldr-directory-path . ,(cache-path "tldr")))

(leaf desktop
  :require t
  :config
  (push '(company-box-mode nil) desktop-minor-mode-table))

;; 语法检查
(leaf flycheck
  :ensure t
  :setq (flycheck-emacs-lisp-load-path . 'inherit)
  :config
  (global-flycheck-mode))

;; 代码片段
(leaf yasnippet
  :ensure t
  :preface
  (defun yas-minor-mode-off () (yas-minor-mode -1))
  :config
  (yas-global-mode))

;; auto format
(leaf apheleia
  :ensure t
  :require t
  :hook (((emacs-lisp-mode-hook
           elixir-mode-hook
           heex-ts-mode-hook
           typescript-mode-hook
           typescript-ts-mode-hook
           tsx-ts-mode-hook
           typescript-tsx-ts-mode-hook) . apheleia-mode))
  ;; language specific bindings should be added to there own settings
  ;; because there is no mode map definition when apheleia is loaded
  ;; but it's ok for emacs lisp
  :bind ((emacs-lisp-mode-map
          ("TAB" . apheleia-tab-and-format-apheleia))
         (lisp-interaction-mode-map
          ("TAB" . apheleia-tab-and-format-apheleia)))
  :preface
  ;; fix apheleia--get-formatters not defined
  (autoload #'apheleia--get-formatters "apheleia-core")
  (defun apheleia-tab-and-format (formatters &optional arg)
    (interactive "P")
    (indent-for-tab-command arg)
    (if (save-excursion ; if blank
          (beginning-of-line)
          (looking-at-p "[[:blank:]]*$"))
        (let* ((indent-level (intern
                              (concat
                               (string-remove-suffix "-mode" (symbol-name major-mode))
                               "-indent-level")))
               (tab-width (if (boundp indent-level)
                              (symbol-value indent-level)
                            tab-width)))
          (insert-tab))
      (apheleia-format-buffer formatters)))
  (defun apheleia-tab-and-format-lsp (&optional arg)
    (interactive "P")
    (apheleia-tab-and-format '(apheleia-lsp) arg))
  (defun apheleia-tab-and-format-apheleia (&optional arg)
    (interactive "P")
    (apheleia-tab-and-format (apheleia--get-formatters) arg))
  (defun apheleia-tab-and-format-apheleia-and-lsp (&optional arg)
    (interactive "P")
    (apheleia-tab-and-format
     (cons 'apheleia-lsp (apheleia--get-formatters))
     arg))
  (defun apheleia-lsp-formatter-buffer (buffer scratch)
    (with-current-buffer buffer
      (if (lsp-feature? "textDocument/formatting")
          (let ((edits (lsp-request "textDocument/formatting"
                                    (lsp--make-document-formatting-params))))
            (unless (seq-empty-p edits)
              (with-current-buffer scratch
                (lsp--apply-text-edits edits 'format)))))))
  (cl-defun apheleia-lsp-formatter
      (&key buffer scratch formatter callback &allow-other-keys)
    (apheleia-lsp-formatter-buffer buffer scratch)
    (funcall callback))
  (defun apheleia--mix-dot-formatter-path (buffer-file-name)
    (if-let* ((buffer-file-name)
              (path (locate-dominating-file buffer-file-name ".formatter.exs")))
        (file-truename path)
      "."))
  :config
  (add-to-list 'apheleia-mode-alist '(emacs-lisp-mode . lisp-indent))
  (add-to-list 'apheleia-formatters '(apheleia-lsp . apheleia-lsp-formatter))
  (setf (alist-get 'mix-format apheleia-formatters)
        `(,(vendor-path "mix-format.sh") (apheleia--mix-dot-formatter-path buffer-file-name) "-")))

;;; Languages
;; elisp
(leaf elisp-demos
  :ensure t
  :init
  (advice-add 'describe-function-1 :after #'elisp-demos-advice-describe-function-1))

;; json
(leaf json-mode :ensure t)

;; Web mode
(leaf web-mode
  :ensure t
  :mode ("\\.erb\\'" "\\.html?\\'" "\\.css\\'" "\\.eex\\'" "\\.leex\\'" "\\.vue\\'")
  :setq ((web-mode-script-padding . 2)
         (web-mode-style-padding . 2)
         (web-mode-block-padding . 2)
         (web-mode-markup-indent-offset . 2)
         (web-mode-css-indent-offset . 2)
         (web-mode-code-indent-offset . 2)
         (web-mode-enable-auto-indentation))
  :config
  (when (buffer-extension-name= "vue")
    (setq web-mode-script-padding 0)))

;; coffee
(leaf coffee-mode
  :ensure t
  :setq ((coffee-tab-width . 2)))

;; Ruby
(leaf enh-ruby-mode
  :ensure t
  :mode ("\\(?:\\.rb\\|ru\\|rake\\|thor\\|jbuilder\\|gemspec\\|podspec\\|/\\(?:Gem\\|Rake\\|Cap\\|Thor\\|Vagrant\\|Guard\\|Pod\\)file\\)\\'")
  :interpreter ("ruby")
  :setq ((enh-ruby-add-encoding-comment-on-save)))

(leaf yard-mode
  :ensure t
  :hook (enh-ruby-mode-hook))

(leaf ruby-end :ensure t)

(leaf robe
  :ensure t
  :hook enh-ruby-mode
  :after company
  :config
  (push '(company-robe :with company-yasnippet) company-backends)
  ;; FIXME may will turn off eldoc, company-box global.
  (eldoc-mode 0)
  (setq company-box-doc-enable nil))

(leaf haml-mode :ensure t)

;; Elixir
(leaf heex-ts-mode
  :ensure t
  :bind ((heex-ts-mode-map
          ("M-o" . nil)
          ("C-c C-v" . nil))))

(leaf elixir-ts-mode
  :ensure t
  :setq (elixir-indent-level . 2)
  :bind ((elixir-ts-mode-map
          ("TAB" . apheleia-tab-and-format-lsp)))
  :init
  (add-to-list 'major-mode-remap-alist '(elixir-mode . elixir-ts-mode))
  :mode (("\\.elixir\\'" "\\.ex\\'" "\\.exs\\'" "mix\\.lock") . elixir-mode))

(leaf elixir-yasnippets :ensure t)

(leaf flycheck-credo
  :ensure t
  :preface
  (defun flycheck-credo-lsp-ui ()
    (flycheck-add-next-checker 'lsp 'elixir-credo))
  :setq (flycheck-elixir-credo-strict . t)
  :hook ((flycheck-mode-hook . flycheck-credo-setup)
         (lsp-after-initialize-hook . flycheck-credo-lsp-ui)))

;; Erlang
(leaf erlang
  :ensure t
  :preface
  ;; use flymake instead of flycheck for erlang-lsp
  (defun do-erlang-mode-hook ()
    (setq-local lsp-prefer-flymake t))
  ;; :setq-default ((erlang-indent-level . 2))
  :mode ("\\rebar.*\\'"
         "relx\\.config$"
         "sys\\.config$"
         "system\\.config$"
         "\\.app\\.src$"
         "vars\\.config$"
         "start\\.script$"
         "start_clean\\.script$"
         "\\.rel$")
  :hook (erlang-mode-hook . do-erlang-mode-hook)
  :config
  (require 'erlang-start)
  (advice-add 'erlang-man-user-local-emacs-dir :override
              (lambda ()
                (cache-path "erlang_mode_man_pages/"))))

(leaf groovy-mode
  :ensure t
  :mode ("\\.groovy\\'"))

;; Clojure
(leaf cider
  :ensure t
  :commands cider
  :hook (clojure-mode-hook))

;; Golang
(leaf go-mode :ensure t)

;; protobuf

(leaf protobuf-mode :ensure t)

;; dockerfile
(leaf dockerfile-mode :ensure t)

;; typescript
(leaf typescript-ts-mode
  :require t
  :bind
  ((typescript-mode-map
    ("TAB" . apheleia-tab-and-format-lsp))
   (typescript-ts-mode-map
    ("TAB" . apheleia-tab-and-format-lsp))
   (tsx-ts-mode-map
    ("TAB" . apheleia-tab-and-format-lsp))
   (typescript-tsx-ts-mode-map
    ("TAB" . apheleia-tab-and-format-lsp)))
  :init
  (setq typescript-indent-level 2)
  (setq typescript-ts-mode-indent-offset 2)
  (add-to-list
   'treesit-language-source-alist
   '(typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")))
  (define-derived-mode typescript-mode typescript-ts-mode "TypeScript")
  (add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode))
  ;; tsx
  (add-to-list
   'treesit-language-source-alist
   '(tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")))
  (define-derived-mode typescript-tsx-ts-mode tsx-ts-mode "TypeScript[TSX]")
  (add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-tsx-ts-mode)))

;; lua
(leaf lua-mode :ensure t)

;; rust
(leaf rust-mode :ensure t)

;; sql
(leaf sql-mode
  :setq (sql-dialect . 'postgres))

(leaf sqlformat :ensure t
  :setq ((sqlformat-command . 'pgformatter)
         (sqlformat-args . '("-s2" "-g")))
  :hook (sql-mode-hook . sqlformat-on-save-mode))


(leaf pg
  :vc (pg :url "https://github.com/emarsden/pg-el.git"))

(leaf pgmacs
  :after pg
  :vc (pgmacs :url "https://github.com/emarsden/pgmacs.git"))

;; nix
(leaf nix-mode :ensure t)

;; copilot
(leaf copilot
  :when (bound-and-true-p copilot-enable)
  :ensure t
  :hook ((prog-mode-hook yaml-mode-hook) . copilot-mode)
  :init
  ;; set yasnippet's priority
  (defun copilot//complete-common ()
    (interactive)
    (unless (ignore-errors (yas-expand))
      (copilot-accept-completion)))
  ;; clear overlay when undo
  (advice-add 'vundo :before #'copilot-clear-overlay)
  :bind ((copilot-completion-map
          ("C-e" . copilot-accept-completion-by-line)
          ("M-f" . copilot-accept-completion-by-word)
          ("<tab>" . copilot//complete-common)
          ("TAB" . copilot//complete-common)))
  :after (company)
  :init (delq 'company-preview-if-just-one-frontend company-frontends))

(leaf minuet
  :when (bound-and-true-p minuet-enable)
  :ensure t
  :hook ((prog-mode-hook yaml-mode-hook) . minuet-auto-suggestion-mode)
  :bind
  ((minuet-active-mode-map
    ("M-p" . #'minuet-previous-suggestion) ;; invoke completion or cycle to next completion
    ("M-n" . #'minuet-next-suggestion) ;; invoke completion or cycle to previous completion
    ("<tab>" . #'minuet-accept-suggestion) ;; accept whole completion
    ("TAB" . #'minuet-accept-suggestion)
    ;; Accept the first line of completion, or N lines with a numeric-prefix:
    ;; e.g. C-u 2 C-e will accepts 2 lines of completion.
    ("C-e" . #'minuet-accept-suggestion-line)
    ("C-g" . #'minuet-dismiss-suggestion)))
  :init
  (setq minuet-provider 'gemini)
  (setq minuet-n-completions 3)
  :config
  (minuet-set-optional-options
   minuet-gemini-options
   :generationConfig
   '(:maxOutputTokens 256 :topP 0.9 :thinkingConfig (:thinkingBudget 0))))

(provide 'program/advanced)
